package com.vision.sales.sv.serv;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.CustomSessionFactoryBean;
import com.vision.core.sv.db.mgr.DataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.serv.util.ServletUtils;
import com.vision.core.sv.util.CollectionUtils;
import com.vision.sales.cm.data.SalesPermissions;
import com.vision.sales.cm.data.TransactionStatus;
import com.vision.sales.cm.db.data.Collection;
import com.vision.sales.cm.db.data.CollectionItem;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.cm.db.data.Salesman;
import com.vision.sales.cm.db.data.SalesmanProduct;
import com.vision.sales.sv.db.mgr.CollectionItemManager;
import com.vision.sales.sv.db.mgr.CollectionManager;
import com.vision.sales.sv.db.mgr.SalesInvoiceManager;
import com.vision.sales.sv.db.mgr.SalesmanManager;
import com.vision.sales.sv.db.mgr.SalesmanProductManager;
import com.vision.sales.sv.report.CommissionReportRow;
import com.vision.sales.sv.report.Report;
import com.vision.sales.sv.report.ReportWriter;

/**
 * Servlet for printing Salesman Commission report.
 * 
 * This servlet is accessible via the /print/commission_report path in the URL (see web.xml)
 * 
 * GET parameters:
 * <ul>
 * 	<li>title - the title that will appear in the report
 * 	<li>span - start and end date covered. Format: yyyyMMdd-yyyyMMdd</li>
 * 	<li>companies - optional comma-separated list of company ids indicating which companies to include. If not specified, all companies will be included.</li>
 * 	<li>customers - optional comma-separated list of customer ids indicating which customers to include. If not specified, all customers will be included.</li>
 * 	<li>types - optional comma-separated list of integers indicating which transaction types to include. If not specified, all types will be included.</li>
 * 	<li>showzeros - optional boolean value to indicate if transactions with zero amounts are to be included. Defaults to true.</li>
 * </ul>
 * 
 * Example:
 * <pre>/print/commission_report?title=Commission%20Report&span=20120101-20121231&companies=1,2&customers=5,10,22&types=0,1,2&showzeros=true</pre>
 * 
 * @author Mark
 */
@Service("commission_report")
public class PrintCommissionReportServlet extends HttpServlet {
	
	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(PrintCommissionReportServlet.class);
	
	@Autowired
	@Qualifier("salesmanManager")
	private DataManager<Salesman> salesmanManager;
	
	@Autowired
	@Qualifier("collectionManager")
	private DataManager<Collection> collectionManager;

	@Autowired
	@Qualifier("collectionItemManager")
	private DataManager<CollectionItem> collectionItemManager;

	@Autowired
	@Qualifier("salesmanProductManager")
	private DataManager<SalesmanProduct> salesmanProductManager;

	@Autowired
	@Qualifier("salesInvoiceManager")
	private DataManager<SalesInvoice> salesInvoiceManager;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			ServletUtils.checkPermissions(SalesPermissions.ViewCommissionReport);
		} catch (RpcException e) {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You are not authorized to perform this operation.");
			return;
		} 
		
		// Parameters --
		log.debug("Parsing parameters from URL...");
		CommissionReportParams params = new CommissionReportParams(req);
		if (params.error != null) {
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, params.error);
			return;
		}
		
		try {
			DateFormat format = new SimpleDateFormat("MMM d, yyyy");
			
			// Build the Report
			log.debug("Building report...");
			Report report = new Report();
			report.setFilename("Commission Report " + params.span);
			report.setSize(params.size);
			report.setOrientation(params.orientation);
			report.setTitle(params.title);
			report.setSubtitle(format.format(params.start) + " - " + format.format(params.end));

			// Columns and Grouping
			report.addStringColumn("salesman", "Salesman", 60, null);
			report.addDoubleColumn("commission", "Commission", 25);
			
			// Data Source
			log.debug("Creating data source...");
			report.setDataSource(getDataSource(params));
			
			// Print and Write to Stream
			log.debug("Writing report to stream...");
			ReportWriter.write(report, resp);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The server was unable to process the request.");
		}
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private JRDataSource getDataSource(CommissionReportParams params) {
		List<Salesman> salesmen = getSalesmen(params.salesmanIds);
		if (salesmen.isEmpty())
			return new JRBeanCollectionDataSource(Collections.emptyList());
		
		List<CollectionItem> collectionItems = getCollectionItems(params.start, params.end);
		if (collectionItems.isEmpty())
			return new JRBeanCollectionDataSource(Collections.emptyList());
		
		Map<Long, Double> collectionsByInvoices = getCollectionsByInvoices(collectionItems);
		Map<Long, List<SalesInvoice>> salesInvoices = getInvoicesBySalesman(new ArrayList(collectionsByInvoices.keySet()));
		Map<String, Double> commissionRates = getCommissionRatesBySalesmanAndProduct();
		
		double invTotalCollection;
		double smCommissionRate;
		double smCommission;
		List<SalesInvoice> smInvoiceList;
		
		List<CommissionReportRow> list = new ArrayList<CommissionReportRow>(salesmen.size());
		
		for (Salesman salesman : salesmen) {
			Long salesmanId = salesman.getId();
			
			if (salesInvoices.containsKey(salesmanId))
				smInvoiceList = salesInvoices.get(salesmanId);
			else	
				smInvoiceList = new ArrayList<SalesInvoice>();

			smCommission = 0;
			for (SalesInvoice salesInvoice : smInvoiceList) {
				Long id = salesInvoice.getId();
				
				// sum up collection for invoice
				if (collectionsByInvoices.containsKey(id))
					invTotalCollection = collectionsByInvoices.get(id);
				else
					invTotalCollection = 0;
					
				String commId = salesmanId+"-"+salesInvoice.getProductId();
				// get commission rate
				if (commissionRates.containsKey(commId)) {
					smCommissionRate = commissionRates.get(commId);
				} else {
					log.info("No commission rate defined for salesman {} and product {}!", salesman.getCode(), salesInvoice.getProduct().getCode());
					smCommissionRate = 0;
				}
				
				// sum up commission
				smCommission += invTotalCollection * smCommissionRate;
			}

			CommissionReportRow row = new CommissionReportRow(salesman.getName(), smCommission);
			list.add(row);
		}
		
		JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(list);
		return ds;
	}

	private Map<Long, Double> getCollectionsByInvoices(List<CollectionItem> collections) {
		Map<Long, Double> map = new HashMap<Long, Double>();
		for (CollectionItem collectionItem : collections) {
			Long salesInvoiceId = collectionItem.getSalesInvoiceId();
			
			double collection = collectionItem.getNetAmount();
			
			if (map.containsKey(salesInvoiceId))
				collection += map.get(salesInvoiceId);
			
			map.put(salesInvoiceId, collection);
		}
		return map;
	}

	@SuppressWarnings("unchecked")
	private List<CollectionItem> getCollectionItems(Date start, Date end) {
		// XXX @Autowired doesn't work, so we use this
		collectionManager = new CollectionManager();
		collectionManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		collectionItemManager = new CollectionItemManager();
		collectionItemManager.setSessionFactory(CustomSessionFactoryBean.getFactory());

		// create filter for collection by inclusive date
		log.debug("Retrieving collections...");
		ISimpleCriteria crit = SimpleCriteria.forClass(Collection.class);
		crit.eq("status", TransactionStatus.POSTED);
		crit.between("date", start, end);
		
		List<Collection> collections = collectionManager.getTemplate().findByCriteria(crit.getDetached());
		if (collections.isEmpty())
			return new ArrayList<CollectionItem>(0);
		
		crit = SimpleCriteria.forClass(CollectionItem.class);
		crit.in("collection.id", CollectionUtils.listFrom(collections, "getId"));
		
		List<CollectionItem> list = collectionItemManager.getTemplate().findByCriteria(crit.getDetached());
		
		return list;
	}
	
	private Map<String, Double> getCommissionRatesBySalesmanAndProduct() {
		salesmanProductManager = new SalesmanProductManager();
		salesmanProductManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		Map<String, Double> salesmanProductCommissions = new HashMap<String, Double>();
		
		log.debug("Retrieving salesman products...");
		ISimpleCriteria pcrit = SimpleCriteria.forClass(SalesmanProduct.class);
		
		@SuppressWarnings("unchecked")
		List<SalesmanProduct> all = salesmanProductManager.getTemplate().findByCriteria(pcrit.getDetached());

		for (SalesmanProduct salesmanProduct : all) {
			Long salesmanId = salesmanProduct.getSalesman().getId();
			Long productId = salesmanProduct.getProduct().getId();
			
			String key = salesmanId + "-" + productId;
			
			salesmanProductCommissions.put(key, salesmanProduct.getCommissionRate() * .01);
		}

		return salesmanProductCommissions;
	}

	@SuppressWarnings("unchecked")
	private List<Salesman> getSalesmen(List<Long> salesmanIds) {
		
		salesmanManager = new SalesmanManager();
		salesmanManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		// create filter for salesman by id
		ISimpleCriteria scrit = SimpleCriteria.forClass(Salesman.class);
		if (salesmanIds != null && !salesmanIds.isEmpty())
			scrit.in("id", salesmanIds);
		
		List<Salesman> list = salesmanManager.getTemplate().findByCriteria(scrit.getDetached());
		
		return list;
	}
	
	

	@SuppressWarnings("unchecked")
	private Map<Long, List<SalesInvoice>> getInvoicesBySalesman(List<Long> invoiceIds) {
		if (invoiceIds.isEmpty())
			return Collections.emptyMap();
		
		// XXX @Autowired doesn't seem to work, thus this patch
		salesInvoiceManager = new SalesInvoiceManager();
		salesInvoiceManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		ISimpleCriteria crit = SimpleCriteria.forClass(SalesInvoice.class);
		crit.eq("status", TransactionStatus.POSTED);
		crit.in("id", invoiceIds);
		
		log.debug("Retrieving sales invoices...");
		List<SalesInvoice> invoices = salesInvoiceManager.getTemplate().findByCriteria(crit.getDetached());

		Map<Long, List<SalesInvoice>> map = new HashMap<Long, List<SalesInvoice>>();
		for (SalesInvoice invoice : invoices) {
			Long salesmanId = invoice.getSalesman().getId();
			List<SalesInvoice> list;
			if (map.containsKey(salesmanId))
				list = map.get(salesmanId);
			else {
				list = new ArrayList<SalesInvoice>();
				map.put(salesmanId, list);
			}
			list.add(invoice);
		}
		return map;
	}

	// Inner classes %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	static class CommissionReportParams extends PeriodSalesReportParams{
		
		String span;
		Date start;
		Date end;
		boolean showZeros = true;
		List<Long> salesmanIds;
		
		public CommissionReportParams(HttpServletRequest req) {
			super(req);
			
			setDefaultTitle("Commission Report");
			
			span = req.getParameter("span");
			try {
				String[] split = span.split("-");
				start = yyyyMMdd.parse(split[0]);
				end = yyyyMMdd.parse(split[1]);
			} catch (Exception e) {
				error = "Unable to parse start and end dates.";
			}
			
			String showZeroes = req.getParameter("showzeroes");
			if (showZeroes != null) {
				try {
					showZeros = Boolean.parseBoolean(showZeroes);
				} catch (Exception e) {
				}
			}
			
			String sSalesmanIds = req.getParameter("salesman");
			if (sSalesmanIds != null)
				salesmanIds = CollectionUtils.longsFromString(sSalesmanIds, ',');
		}
		
	}

}
